package com.intellif.nio.server1;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author by mozping
 * @Classname NioServer
 * @Description TODO
 * @Date 2019/11/27 9:30
 */
public class NioServer {

    public static void main(String[] args) throws Exception {

        //定义好五个端口
        int[] ports = new int[]{10000, 10001, 10002, 10003, 10004, 10005};

        //创建一个Selector
        Selector selector = Selector.open();

        //依次创建5个监听的 serverSocketChannel，且都注册到一个 selector 选择器
        for (int port : ports) {

            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //端口绑定，通过 ServerSocketChannel 关联的 ServerSocket 绑定
            ServerSocket serverSocket = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(port);
            serverSocket.bind(address);

            // Channel注册到 selector ，这里是服务端，因此是 ServerSocketChannel 注册到 Selector，其实
            //客户端的 SocketChannel 也可以注册，因此 NIO 的客户端也可以用非阻塞的模式实现
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            //每次当一个 channel 注册到一个selector，都会创建一个selectionKey
            //selectionKey会有效，直到调用close、cancel、关闭这个selector，下一次使用的时候才会移除
            //selection key包含两个集合，一个是 interest set 和 ready set (感兴趣集合和准备操作集合)
            //selection key 可以通过 attach 方法管理一些数据
            //最重要的是：selectionKey 可以获取到和它关联的 channel 对象，便于直到应该由哪个channel来处理事件
        }

        while (true) {
            //select 是阻塞方法,有事件就返回
            int num = selector.select();

            //获取事件,可能多个通道有事件，因此返回的是一个集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //可接受事件
                if (selectionKey.isAcceptable()) {
                    //拿到channel对象
                    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();

                    //得到 SocketChannel ，代表 TCP 连接对象
                    SocketChannel socketChannel = channel.accept();

                    //配置非阻塞，由此可以看到客户端 socketChannel 也可以是非阻塞的，
                    // configureBlocking 方法实际上定义在父类，因此客户端服务端都是非阻塞的
                    socketChannel.configureBlocking(false);

                    //也把连接对象注册到selector,连接对象关心的应该是读写事件
                    socketChannel.register(selector, SelectionKey.OP_READ);

                    //移除非常关键,因此这个连接事件已经处理了，不移除的话会多次处理
                    iterator.remove();

                    System.out.println("获取到客户端的连接: " + socketChannel);
                } else if (selectionKey.isReadable()) {
                    //拿到channel对象
                    SocketChannel channel = (SocketChannel) selectionKey.channel();

                    int byteRead = 0;
                    while (true) {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                        byteBuffer.clear();

                        int read = channel.read(byteBuffer);

                        if (read < 0) {
                            break;
                        }
                        byteBuffer.flip();
                        channel.write(byteBuffer);
                        byteRead += read;
                    }
                    System.out.println("读取：" + byteRead + ", 来自于 " + channel);
                    iterator.remove();
                }
            }
        }
    }
}